#include "CPU.h"
#include "base/Configuration.h"
#include "base/QuiesceRequest.h"
#include "base/QuiesceResponse.h"
#include "base/RawFlushRequest.h"
#include "base/Response.h"
#include "base/Simulator.h"
#include "mem/InvalidateRequest.h"
#include "mem/IsDirtyRequest.h"
#include "mem/Memory.h"
#include "mem/UpdateRequest.h"
#include "mem/VerifyRequest.h"
#include "mem/RawMemoryRequest.h"

NEW_DELETE_OPERATORS_CPP(Memory,10);

Memory::Memory (const char* name, uint latency_) :
    Entity(name, CLOCK_WITH_MEMORY),
    latency(latency_),
    requests(NEW List<TimedRequest*>),
    responses(NEW List<Response*>),
    memoryLog(NEW Logger("memoryLog", "m", "-:w,log.sim", "Memory tracing")),
    numReservations(0),
    reserve((bool*)CALLOC(global_sim->getNumCpus(), sizeof(bool))),
    reserve_addr((PA*)CALLOC(global_sim->getNumCpus(), sizeof(PA)))
{
    registerPacketHandler(typeid(LoadRequest),
                          (PacketHandlerPtr)&Memory::handle_DataRequest);
    registerPacketHandler(typeid(StoreRequest),
                          (PacketHandlerPtr)&Memory::handle_DataRequest);
    registerPacketHandler(typeid(RawFlushRequest),
                          (PacketHandlerPtr)&Memory::handle_RawFlushRequest);
    registerPacketHandler(typeid(QuiesceRequest),
                          (PacketHandlerPtr)&Memory::handle_QuiesceRequest);
    registerPacketHandler(typeid(RawMemoryRequest),
                          (PacketHandlerPtr)&Memory::handle_RawMemoryRequest);

    ignorePacketType(typeid(InvalidateRequest));
    ignorePacketType(typeid(VerifyRequest));
    ignorePacketType(typeid(UpdateRequest));
    ignorePacketType(typeid(IsDirtyRequest));
}

Memory::~Memory () {
    List<TimedRequest*>::iterator trIdx;
    for (trIdx = requests->begin();
         trIdx != requests->end();
         trIdx++)
    {
        TimedRequest* tr = *trIdx;
        ASSERT(tr->request != NULL);
        delete tr->request;
        FREE(tr);
    }

    delete requests;

    List<Response*>::iterator respIdx;
    for (respIdx = responses->begin();
         respIdx != responses->end();
         respIdx++)
    {
        Response* resp = (Response*)(*respIdx);
        DataRequest* req = dynamic_cast<DataRequest*>(resp->getRequest());
        if (req != NULL) {
            req->recursiveDelete();
        }
        delete ((Response*)(*respIdx))->getRequest();
        delete *respIdx;
    }

    delete responses;
    if (reserve) FREE(reserve);
    if (reserve_addr) FREE(reserve_addr);

    delete memoryLog;
}

Result Memory::handle_reservations (DataRequest* r) {
    // should only be called if this request came from a Proc
    Processor* P = CAST_OR_DIE(Processor*, r->getSource());
    int pid = P->getID();

    if (IS_A(LoadRequest*, r)) {
        if (!reserve[pid]) {
            numReservations++;
        }
        reserve[pid] = true;
        reserve_addr[pid] = CONVERT_TO_RESERVATION_GRANULE(r->getAddress());
        memoryLog->d("%d:%lld:%.16llX RESRV| Reserving %08x(%08x).\n",
                     pid,
                     global_sim->getSimclock(),
                     P->getCPU()->getPC(),
                     (uint)reserve_addr[pid],
                     (uint)r->getAddress());
        return success;
    } else if (IS_A(StoreRequest*, r)) {
        if (!reserve[pid] ||
            (reserve_addr[pid] != CONVERT_TO_RESERVATION_GRANULE(r->getAddress())))
        {
            memoryLog->d("%d:%lld:%.16llX RESRV| Store to %08x failed.\n",
                         pid,
                         global_sim->getSimclock(),
                         P->getCPU()->getPC(),
                         (uint)r->getAddress());
            return failure;
        } else {
            reserve[pid] = false;
            numReservations--;
            memoryLog->d("%d:%lld:%.16llX RESRV| Store to %08x succeeded.\n",
                         pid,
                         global_sim->getSimclock(),
                         P->getCPU()->getPC(),
                         (uint)r->getAddress());
        }
        return success;
    } else {
        ASSERT(false);
    }
    return failure; // avoid warning
}

bool Memory::cmpMemory (uint32* dest,
                        const uint32* src,
                        uint size,
                        LineMask const & mask,
                        bool tester)
{
    for (uint i = 0; i < size/WORD_SIZE; i++) {
        if (mask.get(i) == tester) {
            if (*(uint32*)((uint32)dest + i*WORD_SIZE) != src[i]) {
                return false;
            }
        }
    }
    return true;
}

void Memory::copyMemory (uint32* dest,
                         const uint32* src,
                         uint size,
                         LineMask const & mask,
                         bool tester)
{
    for (uint i = 0; i < size/WORD_SIZE; i++) {
        if (mask.get(i) == tester) {
            *(uint32*)((uint32)dest + i*WORD_SIZE) = src[i];
        }
    }
}

void Memory::performOperation (DataRequest* r) {
    memoryLog->d("M:%lli:%0llx:%s MEMORY",
                 global_sim->getSimclock(),
                 r->getAddress(),
                 r->getSource()->getName());

    if (IS_A(LoadRequest*, r)) {
        LoadRequest* lr = (LoadRequest*)r;

        if (lr->aborted) {
            // this shouldn't happen. right?
            // right, this is an obsolete code.
            ASSERT(false);
            // this means that this request is once aborted by other caches
            // for write back. this request packet contains the data to
            // write back. apply it first.
            memcpy(ADDR_UINT32STAR(lr->getAddress()),
                   (uint32*)lr->getData(),
                   lr->getDataSize());
        }

        memcpy((uint*)lr->getData(),
               ADDR_UINT32STAR(lr->getAddress()),
               lr->getDataSize());
        memoryLog->d(" LOAD\n");
        memoryLog->d("   ");
        for (uint j = 0; j < lr->getDataSize()/WORD_SIZE; j++) {
            memoryLog->d("%08x ", ADDR_UINT32STAR(r->getAddress())[j]);
        }
        memoryLog->d("\n");
    } else if (IS_A(StoreRequest*, r)) {
        if (r->isFlushed()) {
            memoryLog->d(" STORE CANCELLED DUE TO EARLY RAW FLUSH\n");
            memoryLog->d("   ");
            for (uint j = 0; j < r->getDataSize()/WORD_SIZE; j++) {
                memoryLog->d("%08x ", ((uint*)r->getData())[j]);
            }
            memoryLog->d("\n");
        } else {
            // revoke any reservations
            int pid = -1;
            Processor* P = dynamic_cast<Processor*>(r->getSource());
            if (P != NULL) { //make sure it is from a Processor
                pid = P->getID();

                // we aren't dealing with reservations
                VA rgranule = CONVERT_TO_RESERVATION_GRANULE(r->getAddress());
                for (uint i = 0;
                     numReservations > 0 && i < global_sim->getNumCpus();
                     i++)
                {
                    if (reserve[i] &&
                        (rgranule == reserve_addr[i]) &&
                        (int)i != pid)
                    {
                        memoryLog->d("%d:%lld:%.16llX RESRV| Revoking reservation for %08x from processor %d.\n",
                                     pid,
                                     global_sim->getSimclock(),
                                     P->getCPU()->getPC(),
                                     (uint)rgranule,
                                     i);
                        reserve[i] = false;
                        numReservations--;
                    }
                }
            }

            copyMemory(ADDR_UINT32STAR(r->getAddress()),
                       (const uint32*)r->getData(),
                       r->getDataSize(),
                       r->getMask(),
                       true);
            if (IS_A(StoreRequest*, r)) {
                memoryLog->d(" STORE\n");
            } else {
                ASSERT(false);
            }
            memoryLog->d("   ");
            for (uint j = 0; j < r->getDataSize()/WORD_SIZE; j++) {
                memoryLog->d("%08x ", ((uint*)r->getData())[j]);
            }
            memoryLog->d("\n");
        }
    } else {
        ASSERT(false);
    }

    // make a response
    Response* resp = makeResponse(r);

    // it is needed because the packet may be modified in another place
    // reached through routePacket()
    char tmp[128];
    strcpy(tmp, r->getSource()->getName());
    VA addr = r->getAddress();

    if (routePacket(resp) == stall) {
        responses->add(resp);
        memoryLog->d("M:%lli:%0llx:%s MEMORY RESPONSE STALLED\n",
                     global_sim->getSimclock(),
                     addr,
                     tmp);
    } else {
        memoryLog->d("M:%lli:%0llx:%s MEMORY RESPONSE SENT\n",
                     global_sim->getSimclock(),
                     addr,
                     tmp);

    }
}

Response* Memory::makeResponse(DataRequest* r) {
    if (IS_A(LoadRequest*, r)) {
        return NEW Response(r, r->getDataSize());
    } else if (IS_A(StoreRequest*, r)) {
        return NEW Response(r, 1);    
    } else {
        ASSERT(false);
        return NULL;
    }
}

void Memory::tick () {
    while (!requests->empty() &&
           global_sim->getSimclock() >= requests->peek()->alarm)
    {
        TimedRequest* tr = requests->remove();
        DataRequest* r = tr->request;

        performOperation(r);
        FREE(tr);
    }

    // try to send the stalled responses
    while (!responses->empty())
    {
        Response* r = responses->remove();

        if (routePacket(r) == stall) {
            responses->push_front(r);
            break;
        }
    }
}

Result Memory::handle_RawMemoryRequest (Packet* r) {
    RawMemoryRequest* rmr = CAST_OR_DIE(RawMemoryRequest*, r);

    // if this causes a segfault, the default handler jmps back to
    // Processor::getMemory() and it returns false to gdb
    //
    memcpy(rmr->getData(),
           ADDR_VOIDSTAR(rmr->getAddress()),
           global_sim->cfg.blocksize);

    return success;
}

Result Memory::handle_QuiesceRequest (Packet* r) {
    quiesceLog->d("Memory::handle_QuiesceRequest\n");
    QuiesceResponse* qr = NEW QuiesceResponse((Request*)r);
    Result p = routePacket(qr);
    ASSERT(p == success);

    return success;
}

Result Memory::handle_RawFlushRequest (Packet* r) {
    RawFlushRequest* rfr = (RawFlushRequest*)r;

    // execute any matching requests in the buffer in order
    List<TimedRequest*>* skippedRequests = NEW List<TimedRequest*>;
    while (!requests->empty()) {
        TimedRequest* tr = requests->remove();
        DataRequest* r = tr->request;
        if (rfr->inBounds(r->getAddress(), r->getDataSize())) {
            performOperation(r);
            FREE(tr);
        }
        else {
            skippedRequests->add(tr);
        }
    }
    delete requests;
    requests = skippedRequests;

    // do not delete the RawFlushRequest.
    // it will be deleted by the sender.
    return success;
}

Result Memory::sinkFailed (Request* req, Response* resp, Result res) {
    ASSERT(res == stall);
    responses->add(resp);
    return success;
}

Result Memory::handle_DataRequest (Packet* r) {
    DataRequest* dr = CAST_OR_DIE(DataRequest*, r);

    // handle any reservations immediately.  This is only used
    // when we're connected directly to the Processors, thus an "ideal"
    // system, so we don't worry about possible delay of handling reservations
    if (dr->isExclusive() &&
        IS_A(Processor*, dr->getSource()))
    {
        if (handle_reservations(dr) == failure) {
            return failure;
        }
    }
    
    enqueueDataRequest(dr);

    return success;
}

void Memory::enqueueDataRequest (DataRequest* dr) {
    if (getLatency() == 0) {
        performOperation(dr);
    } else {
        TimedRequest* tr = (TimedRequest*)CALLOC(1, sizeof(TimedRequest));
        tr->request = dr;
        tr->alarm = global_sim->getSimclock() + getLatency();

        requests->add(tr);
    }
}
